package com.merry.sb.redis;

import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisClusterNode;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.*;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.TimeUnit;

/**
 * @author zouyb
 * redis的常见命令的redisTemplate实现。
 * 小结：通过redisTemplate获取到操作某个数据类型的实现类，比如【HashOperations】。这个实现类中有专门为hash操作的方法。
 * 或者调用【redisTemplate.execute()】方法自己实现。实际上那些操作类也多是采用【execute】方法实现的。
 */
public class RedisTemplateDemo {
    @Resource
    RedisTemplate redisTemplate;

    public static void main(String[] args) {
        RedisTemplateDemo jedisDemo = new RedisTemplateDemo();
        jedisDemo.dealRedisString();
    }


    /**
     * redis处理字符串类型的RedisTemplate代码实现。
     *      set：设置键的值。【set k1 v1】
     *      get：获取键的值。【get key】
     *      incr：将键的整数值加1。（仅限于整数）【incr key】
     *      decr：将键的整数值减1。（仅限于整数）【decr key】
     *      incrby：同上，只是需要自己设置步数。【incrby key 2】
     *      decrby：同上。【decrby key 2】
     *      mset：同时设置多个键值对。【mset k1 v1 k2 v2】
     *      mget：同时获取多个键值对。【mget k1 k2】
     *      setnx：只有在键不存在时，才设置，nx代表“set if not exists”。【setnx k1 v1】
     *      setex：设置键的值，并且设置过期时间。【setex k1 10 v1】
     *      getset：将键的值设置为新值，并返回旧的值。【getset k1 newv1】
     *
     */
    private void dealRedisString() {
        //获取操作字符串类型的实现类。
        ValueOperations valueOperations = redisTemplate.opsForValue();
        valueOperations.set("k1","v1");
        valueOperations.get("k1");
        valueOperations.increment("k1",2);
        valueOperations.decrement("k1",2);
        Set<String> keys = new HashSet<>();keys.add("k1");keys.add("k2");
        valueOperations.multiGet(keys);
        HashMap<String,String> map = new HashMap<>();map.put("k1","v1");map.put("k2","v2");
        valueOperations.multiSet(map);
        valueOperations.setIfAbsent("k1","v1",10,TimeUnit.SECONDS);
        valueOperations.set("k1","v1",10,TimeUnit.SECONDS);
        valueOperations.getAndSet("k1","v1");
        //execute方法的实现。
        redisTemplate.execute(new RedisCallback() {
            @Override
            public Object doInRedis(RedisConnection connection) throws DataAccessException {
                connection.set("k1".getBytes(),"v1".getBytes());
                return null;
            }
        });
    }

    /**
     * redis处理哈希类型的RedisTemplate代码实现
     *      hset：设置哈希字段的值。【hset k1 field value】。k1找到对应的hash，filed就是当前value的key。
     *      hget：获取哈希字段的值。【hget k1 field】
     *      hgetall：获取哈希表中所有字段和值。【hgetall k1】
     *      hdel：删除一个或多个哈希字段。【hdel k1 field1 field2】
     *      hexists：检查哈希字段是否存在。【hexists k1 field】
     *      hincrby：为哈希表中的字段加上指定的增量。【hincrby key field 2】
     *      hsetnx：只有字段不存在时，才设置哈希字段的值。【hsetnx key field value】
     *
     */
    private void dealRedisHash() {
        //获取操作哈希的实现类。
        HashOperations hashOperations = redisTemplate.opsForHash();
        hashOperations.put("k1","field","value");
        hashOperations.get("k1","field");
        hashOperations.entries("k1");
        hashOperations.delete("k1","field1","field2");
        hashOperations.hasKey("k1","field1");
        hashOperations.increment("k1","field",10);
        hashOperations.putIfAbsent("k1","field","value");
        //execute方法实现。
        redisTemplate.execute(new RedisCallback() {
            @Override
            public Object doInRedis(RedisConnection connection) throws DataAccessException {
                connection.hGet("k1".getBytes(),"field".getBytes());
                return null;
            }
        });
    }

    /**
     * redis处理列表类型的RedisTemplate代码实现
     *      lpush：在列表头部插入一个或多个值。【lpush k1 v1 v2】
     *      rpush：在列表尾部插入一个或多个值。【rpush k1 v1 v2】
     *      lpop：移除并返回列表的头元素。【lpop k1】
     *      rpop：移除并返回列表的尾元素。【rpop k1】
     *      lrange：获取列表中指定范围的元素，索引从0开始，负索引表示从列表尾部开始。【lrange k1 0 10】
     *      llen：获取列表的长度。【llen k1】
     *      lrem：根据参数count的值，移除列表中与参数value相等的元素。【lrem k1 c1 v1】。
     *          c1=0，表示移除列表中所有的v1。c1>0，表示从右边开始，移除c1个v1。c1<0，与c1>0相反。
     *
     */
    private void dealRedisLists() {
        //获取list的操作实现类。
        ListOperations listOperations = redisTemplate.opsForList();
        //放一个。
        listOperations.leftPush("k1","v1");
        //放多个。
        listOperations.leftPushAll("k1","v1","v2");
        listOperations.rightPush("k1","v1");
        listOperations.leftPop("k1");
        listOperations.rightPop("k1");
        listOperations.range("k1",0,10);
        listOperations.size("k1");
        listOperations.remove("k1",0,"v1");
        //execute方法的实现。
        redisTemplate.execute(new RedisCallback() {
            @Override
            public Object doInRedis(RedisConnection connection) throws DataAccessException {
                connection.lPop("k1".getBytes());
                connection.rPush("k1".getBytes(),"v1".getBytes(),"v2".getBytes());
                return null;
            }
        });
    }

    /**
     * redis处理集合类型的RedisTemplate代码实现
     *      sadd：向集合中添加一个或多个成员。【sadd k1 v1 v2】
     *      srem：移除集合中的一个或多个成员。【srem k1 v1 v2】
     *      smembers：返回集合中的所有成员。【smembers k1】
     *      sIsMember：检查某个值是否是集合的成员。【sismember k1 v1】
     *      scard：获取集合中的成员数。【scard k1】
     *      sunion：返回给定集合的并集。【sunion k1 k2】
     *      sinter：返回给定集合的并集。【sinter k1 k2】
     *      sdiff：返回第一个集合与其他集合的差集。【soff k1 k2】
     *
     */
    private void dealRedisSets() {
        //获取set的操作实现类。
        SetOperations setOperations = redisTemplate.opsForSet();
        setOperations.add("k1","v1","v2");
        setOperations.remove("k1","v1","v2");
        setOperations.members("k1");
        setOperations.isMember("k1","v1");
        //scard命令的方法
        setOperations.size("k1");
        setOperations.union("k1","k2");
        setOperations.intersect("k1","k2");
        setOperations.difference("k1","k2");
        //execute方法的实现。
        redisTemplate.execute(new RedisCallback() {
            @Override
            public Object doInRedis(RedisConnection connection) throws DataAccessException {
                connection.sAdd("k1".getBytes(),"v1".getBytes());
                //...这里可以实现上述的所有方法。不在书写了。
                return null;
            }
        });
    }

    /**
     * redis处理有序集合类型的RedisTemplate代码实现
     *      zadd：向有序集合中添加一个或多个成员。或者更新已经存在成员的分数。【zadd k1 s1 v1 s2 v2】。s1就是v1的分数，排序就是按照分数来排序的。
     *      zrange：返回有序集合中指定区间内的成员，默认按照从小到大排序。【zrange key 0 10】
     *      zrem：移除有序集合中的一个或多个成员。【zrem k1 v1 v2】
     *      zscore：返回有序集合中指定成员的分数。【zscore k1 v1】
     *      zrangebyscore：返回有序集合中指定分数区间内的成员。【zrangebyscore k1 min max】
     *      zcount：返回有序集合中指定分区区间内成员数量。【zcount k1 min max】
     *
     */
    private void dealRedisZSets() {
        //首先获取操作zset的实现类。
        ZSetOperations zSetOperations = redisTemplate.opsForZSet();
        final Set<DefaultTypedTuple> set = new HashSet<>();set.add(new DefaultTypedTuple("v1",12.00));set.add(new DefaultTypedTuple("v2",20.00));
        //在zset实现类中调用这些方法。
        zSetOperations.add("k1",set);
        zSetOperations.range("k1",0,10);
        zSetOperations.remove("k1","v1","v2");
        zSetOperations.score("k1","v1");
        zSetOperations.rangeByScore("k1",12.00,20.00);
        zSetOperations.count("k1",12.00,20.00);
        //还是一样execute方法实现。
        redisTemplate.execute(new RedisCallback() {
            @Override
            public Object doInRedis(RedisConnection connection) throws DataAccessException {
                connection.zAdd("k1".getBytes(),12.00,"v1".getBytes());
                connection.zRange("k1".getBytes(),0,10);
                connection.zRem("k1".getBytes(),"v1".getBytes(),"v2".getBytes());
                connection.zScore("k1".getBytes(),"v1".getBytes());
                connection.zRangeByScore("k1".getBytes(),12.00,20.00);
                connection.zCount("k1".getBytes(),12.00,20.00);
                return null;
            }
        });
    }

    /**
     * redis处理发布订阅类型的RedisTemplate代码实现
     *      publish：将消息发送到指定的频道。【publish channel message】
     *      subscribe：订阅一个或多个频道。【subscribe channel1 channel2】
     *      unsubscribe：退订一个或多个频道。【unsubscribe channel1 channel2】
     *
     */
    private void dealRedisPubSub() {
        //publish命令对应的方法。这个表示向”c1“频道中发布一个消息。只要订阅了c1频道的事物都会收到一个消息。
        redisTemplate.convertAndSend("c1","这是一个消息！");
        //至于订阅和退订消息，需要使用【RedisMessageListenerContainer】来实现。代码见【RedisConfig】。
    }

    /**
     * redis处理键操作的RedisTemplate代码实现
     *      del：删除一个或多个键。 【del key1 key2】
     *      exists：检查一个键是否存在。 【exists key】
     *      expire：设置键的过期时间。【expire key 10】
     *      ttl：获取键的剩余过期时间，如果键不存在则返回-2，如果键没有设置过期时间则返回-1。【ttl key】
     *      type：获取键的存储类型。【type key】
     *
     */
    private void dealRedisKey() {
        //删除一个，
        redisTemplate.delete("k1");
        //删除多个。
        ArrayList<String> keys = new ArrayList<>(); keys.add("k1"); keys.add("k2");
        redisTemplate.delete(keys);
        //检查一个键是否存在。
        redisTemplate.hasKey("k1");
        //设置过期时间
        redisTemplate.expire("k1",10, TimeUnit.SECONDS);
        //获取键的剩余过期时间。
        redisTemplate.getExpire("k1");
        //获取键的存储类型。
        redisTemplate.type("k1");
        //redisTemplate的另一种写法。这种写法就很类似于jedis了。
        redisTemplate.execute(new RedisCallback() {
            @Override
            public Object doInRedis(RedisConnection connection) throws DataAccessException {
                connection.del("k1".getBytes());
                connection.exists("k1".getBytes());
                connection.ttl("k1".getBytes());
                connection.type("k1".getBytes());
                return null;
            }
        });
    }

    /**
     * redis处理一般命令的RedisTemplate代码实现
     *      ping：检测服务器是否在运行。
     *      select 1：选择数据库，redis默认有16个数据库（0-15）。
     *      flushdb：清空当前数据库中的所有键。
     *      flushall：清空所有数据库中的所有键。
     *
     */
    private void dealRedisCommon() {
        //新建一个redis节点。
        RedisClusterNode redisClusterNode = new RedisClusterNode("127.0.0.1", 6379);
        //ping命令
        redisTemplate.opsForCluster().ping(redisClusterNode);
        //flushdb命令
        redisTemplate.opsForCluster().flushDb(redisClusterNode);
        //redisTemplate 的另一种写法。调用execute方法执行一些稀有（ping、select）或者复杂（pipeline）的命令。
        redisTemplate.execute(new RedisCallback() {
            @Override
            public Object doInRedis(RedisConnection connection) throws DataAccessException {
                connection.select(1);
                connection.flushAll();
                connection.flushDb();
                connection.ping();
                return null;
            }
        });
    }
}
